package com.dq.controller.client;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.*;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * Created by EDZ on 2018/3/17.
 *
 * @author zhangzeli
 */
public class Client {

    private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
    public static final String QOP = "auth,auth-int";
    public static final String AUTH = "auth";

    public static void main(String[] args) throws Exception {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        String postUrl = "http://127.0.0.1:8080/System/Register.do";
        HttpPost httpPost = new HttpPost(postUrl);
        httpPost.addHeader("User-Agent", postUrl);
        //如果有请求body
        JSONObject jsonParam = new JSONObject();
        jsonParam.put("u", "admin");
        jsonParam.put("p", "123456");
        //解决中文乱码问题
        StringEntity entity = new StringEntity(jsonParam.toString(), "utf-8");
        entity.setContentEncoding("UTF-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        HttpResponse response = httpclient.execute(httpPost);
        LOGGER.info("响应码：" + response.getStatusLine().getStatusCode());
        // 如果服务端返回401（鉴权失败）
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
            // 服务端响应头中会带有一个WWW-Authenticate的信息
            Header[] headers = response.getAllHeaders();
            Header authHeader = headers[0];
            // WWW-Authenticate value中有很多信息，如nonce、qop、opaque、realm信息
            Map<String, String> maps = getMapByKeyArray(authHeader.getValue().substring(6, authHeader.getValue().length()).split(","));
            //客户端已知信息
            maps.put("username", "zhangzeli");
            maps.put("upassword", "123456");
            maps.put("nc", "00000002");
            //这是一个不透明的字符串值，由客户端提供，并且客户端和服务器都会使用，
            // 以避免用明文文本。这使得双方都可以查验对方的身份，并对消息的完整性提供一些保护；
            String cnonce = Md5Object.getNonce();
            maps.put("cnonce", cnonce);
            // 客户端已知信息
            maps.put("uri", "/System/Register.do");

            maps.put("response", getResponse(maps, jsonParam.toJSONString()));
            // 开始拼凑Authorization 头信息
            StringBuffer authorizationHaderValue = new StringBuffer();
            authorizationHaderValue
                    .append("Digest username=")
                    .append(maps.get("username")).append(", ")
                    .append("realm=").append(maps.get("realm")).append(",")
                    .append("nonce=").append(maps.get("nonce")).append(",")
                    .append("uri=").append(maps.get("uri")).append(",")
                    .append("response=").append(maps.get("response")).append(",")
                    .append("opaque=").append(maps.get("opaque")).append(",")
                    .append("qop=").append(maps.get("qop")).append(",")
                    .append("nc=").append(maps.get("nc")).append(",")
                    .append("cnonce=").append(maps.get("cnonce"));
            LOGGER.info(authorizationHaderValue.toString());
            // 添加到请求头中
            httpPost.addHeader("Authorization", authorizationHaderValue.toString());
            // 请求资源
            response = httpclient.execute(httpPost);
            // 打印响应码
            LOGGER.info("响应码：" + response.getStatusLine().getStatusCode());
            // 打印响应的信息
            LOGGER.info(readResultStreamString(response.getEntity(), httpclient));
        }
    }

    /**
     * 通过HTTP 摘要认证的算法得出response
     * 如果qop=auth-int，在计算ha2时，除了包括HTTP方法，URI路径外，
     * 还包括请求实体主体，从而防止PUT和POST请求表示被人篡改。
     *
     * @param maps
     * @param body
     * @return
     * @throws Exception
     */
    public static String getResponse(Map<String, String> maps, String body) throws Exception {
        String hA1 = Md5Object.encrypt(maps.get("username") + ":" + maps.get("realm") + ":" + "123456");

        LOGGER.info("HA1：[" + maps.get("username") + ":" + maps.get("realm") + ":" + "123456" + "]-->[" + hA1 + "]");

        String qop = maps.get("qop");

        //计算HA2和response
        String hA2;
        //如果 qop 值为“auth”或未指定
        if (!qop.contains(QOP)) {
            hA2 = Md5Object.encrypt("POST:" + maps.get("uri"));
            LOGGER.info("HA2:[" + "POST:" + maps.get("uri") + "]--->[" + hA2 + "]");
        } else {
            if (StringUtils.isEmpty(body)) {
                body = Md5Object.encrypt("");
            } else {
                body = Md5Object.encrypt(body);
            }
            hA2 = Md5Object.encrypt("POST:" + maps.get("uri") + ":" + body);
            LOGGER.info("HA2:[" + "POST:" + maps.get("uri") + ":" + body + "]--->[" + hA2 + "]");
        }
        //计算response
        String response;
        //只要qop为auth或auth-int，就要使用这种方式。
        // 它向摘要中添加了随机计数、qop和cnonce数据
        if (qop.contains(AUTH)) {
            response = Md5Object.encrypt(hA1 + ":" + maps.get("nonce") + ":" + maps.get("nc") + ":" + maps.get("cnonce") + ":" + qop + ":" + hA2);
            LOGGER.info("sresponse:[" + hA1 + ":" + maps.get("nonce") + ":" + maps.get("nc") + ":" + maps.get("cnonce") + ":" + qop + ":" + hA2 + "]-->[" + response + "]");
        } else {
            response = Md5Object.encrypt(hA1 + ":" + maps.get("nonce") + ":" + hA2);
            LOGGER.info("sresponse:[" + hA1 + ":" + maps.get("nonce") + ":" + hA2 + "]-->[" + response + "]");
        }
        return response;
    }

    public static Map<String, String> getMapByKeyArray(String[] resourceStr) {
        Map<String, String> maps = new HashMap<String, String>(8);
        LOGGER.info("***********" + Arrays.toString(resourceStr));
        Arrays.asList(resourceStr).stream().forEach(key ->
                {
                    if (key.contains(AUTH)) {
                        maps.put("qop", QOP);
                    } else {
                        maps.put(key.split("=")[0].trim(), key.split("=")[1].trim());
                    }
                }
        );
        maps.forEach((k, v) -> LOGGER.info(k + "--" + v));
        return maps;
    }

    /**
     * 用于读取字符串响应结果
     *
     * @return String
     * @throws IOException
     */

    protected static String readResultStreamString(HttpEntity httpEntity, CloseableHttpClient defaultHttpClient) throws IOException {
        String result = null;
        InputStream resultStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            resultStream = httpEntity.getContent();

            outputStream = new ByteArrayOutputStream(45555);

            byte[] temp = new byte[4096];
            int length = resultStream.read(temp);
            while (length > 0) {
                outputStream.write(temp, 0, length);

                length = resultStream.read(temp);
            }
            defaultHttpClient.close();
        } catch (IOException ioEx) {
            throw new IOException(ioEx);
        } finally {
            if (null != resultStream) {
                try {
                    resultStream.close();
                } catch (Exception ex) {
                    resultStream = null;
                }
            }
        }
        if (null != outputStream) {
            result = outputStream.toString();
        }
        return result;
    }
}
